This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

Data visualisation

library(tidyverse)
tidyverse_packages()  # which packages are in tidyverse
 [1] "broom"     "dplyr"     "forcats"   "ggplot2"   "haven"     "httr"      "hms"       "jsonlite" 
 [9] "lubridate" "magrittr"  "modelr"    "purrr"     "readr"     "readxl"    "stringr"   "tibble"   
[17] "rvest"     "tidyr"     "xml2"      "tidyverse"

Where can I find useful packages?

Where can I find how to use packages

  • Reference manual on CRAN
  • Vignettes
  • ?
  • Demos
# List vignettes from all *attached* packages
vignette(all = FALSE)
# List vignettes from all *installed* packages (can take a long time!):
vignette(all = TRUE)
# find vignettes of "ggplot2"
vignette(package = "ggplot2")
# view vignette "ggplot2-specs"  
vignette("ggplot2-specs")

now look for more information on ggplot

demo(graphics)  # A show of some of R's graphics capabilities, run in console


    demo(graphics)
    ---- ~~~~~~~~

> #  Copyright (C) 1997-2009 The R Core Team
> 
> require(datasets)

> require(grDevices); require(graphics)

> ## Here is some code which illustrates some of the differences between
> ## R and S graphics capabilities.  Note that colors are generally specified
> ## by a character string name (taken from the X11 rgb.txt file) and that line
> ## textures are given similarly.  The parameter "bg" sets the background
> ## parameter for the plot and there is also an "fg" parameter which sets
> ## the foreground color.
> 
> 
> x <- stats::rnorm(50)

> opar <- par(bg = "white")

> plot(x, ann = FALSE, type = "n")

> abline(h = 0, col = gray(.90))

> lines(x, col = "green4", lty = "dotted")

> points(x, bg = "limegreen", pch = 21)

> title(main = "Simple Use of Color In a Plot",
+       xlab = "Just a Whisper of a Label",
+       col.main = "blue", col.lab = gray(.8),
+       cex.main = 1.2, cex.lab = 1.0, font.main = 4, font.lab = 3)

> ## A little color wheel.   This code just plots equally spaced hues in
> ## a pie chart.   If you have a cheap SVGA monitor (like me) you will
> ## probably find that numerically equispaced does not mean visually
> ## equispaced.  On my display at home, these colors tend to cluster at
> ## the RGB primaries.  On the other hand on the SGI Indy at work the
> ## effect is near perfect.
> 
> par(bg = "gray")

> pie(rep(1,24), col = rainbow(24), radius = 0.9)


> title(main = "A Sample Color Wheel", cex.main = 1.4, font.main = 3)

> title(xlab = "(Use this as a test of monitor linearity)",
+       cex.lab = 0.8, font.lab = 3)

> ## We have already confessed to having these.  This is just showing off X11
> ## color names (and the example (from the postscript manual) is pretty "cute".
> 
> pie.sales <- c(0.12, 0.3, 0.26, 0.16, 0.04, 0.12)

> names(pie.sales) <- c("Blueberry", "Cherry",
+             "Apple", "Boston Cream", "Other", "Vanilla Cream")

> pie(pie.sales,
+     col = c("purple","violetred1","green3","cornsilk","cyan","white"))


> title(main = "January Pie Sales", cex.main = 1.8, font.main = 1)

> title(xlab = "(Don't try this at home kids)", cex.lab = 0.8, font.lab = 3)

> ## Boxplots:  I couldn't resist the capability for filling the "box".
> ## The use of color seems like a useful addition, it focuses attention
> ## on the central bulk of the data.
> 
> par(bg="cornsilk")

> n <- 10

> g <- gl(n, 100, n*100)

> x <- rnorm(n*100) + sqrt(as.numeric(g))

> boxplot(split(x,g), col="lavender", notch=TRUE)


> title(main="Notched Boxplots", xlab="Group", font.main=4, font.lab=1)

> ## An example showing how to fill between curves.
> 
> par(bg="white")

> n <- 100

> x <- c(0,cumsum(rnorm(n)))

> y <- c(0,cumsum(rnorm(n)))

> xx <- c(0:n, n:0)

> yy <- c(x, rev(y))

> plot(xx, yy, type="n", xlab="Time", ylab="Distance")


> polygon(xx, yy, col="gray")

> title("Distance Between Brownian Motions")

> ## Colored plot margins, axis labels and titles.   You do need to be
> ## careful with these kinds of effects.   It's easy to go completely
> ## over the top and you can end up with your lunch all over the keyboard.
> ## On the other hand, my market research clients love it.
> 
> x <- c(0.00, 0.40, 0.86, 0.85, 0.69, 0.48, 0.54, 1.09, 1.11, 1.73, 2.05, 2.02)

> par(bg="lightgray")

> plot(x, type="n", axes=FALSE, ann=FALSE)


> usr <- par("usr")

> rect(usr[1], usr[3], usr[2], usr[4], col="cornsilk", border="black")

> lines(x, col="blue")

> points(x, pch=21, bg="lightcyan", cex=1.25)

> axis(2, col.axis="blue", las=1)

> axis(1, at=1:12, lab=month.abb, col.axis="blue")

> box()

> title(main= "The Level of Interest in R", font.main=4, col.main="red")

> title(xlab= "1996", col.lab="red")

> ## A filled histogram, showing how to change the font used for the
> ## main title without changing the other annotation.
> 
> par(bg="cornsilk")

> x <- rnorm(1000)

> hist(x, xlim=range(-4, 4, x), col="lavender", main="")


> title(main="1000 Normal Random Variates", font.main=3)

> ## A scatterplot matrix
> ## The good old Iris data (yet again)
> 
> pairs(iris[1:4], main="Edgar Anderson's Iris Data", font.main=4, pch=19)


> pairs(iris[1:4], main="Edgar Anderson's Iris Data", pch=21,
+       bg = c("red", "green3", "blue")[unclass(iris$Species)])


> ## Contour plotting
> ## This produces a topographic map of one of Auckland's many volcanic "peaks".
> 
> x <- 10*1:nrow(volcano)

> y <- 10*1:ncol(volcano)

> lev <- pretty(range(volcano), 10)

> par(bg = "lightcyan")

> pin <- par("pin")

> xdelta <- diff(range(x))

> ydelta <- diff(range(y))

> xscale <- pin[1]/xdelta

> yscale <- pin[2]/ydelta

> scale <- min(xscale, yscale)

> xadd <- 0.5*(pin[1]/scale - xdelta)

> yadd <- 0.5*(pin[2]/scale - ydelta)

> plot(numeric(0), numeric(0),
+      xlim = range(x)+c(-1,1)*xadd, ylim = range(y)+c(-1,1)*yadd,
+      type = "n", ann = FALSE)


> usr <- par("usr")

> rect(usr[1], usr[3], usr[2], usr[4], col="green3")

> contour(x, y, volcano, levels = lev, col="yellow", lty="solid", add=TRUE)

> box()

> title("A Topographic Map of Maunga Whau", font= 4)

> title(xlab = "Meters North", ylab = "Meters West", font= 3)

> mtext("10 Meter Contour Spacing", side=3, line=0.35, outer=FALSE,
+       at = mean(par("usr")[1:2]), cex=0.7, font=3)

> ## Conditioning plots
> 
> par(bg="cornsilk")

> coplot(lat ~ long | depth, data = quakes, pch = 21, bg = "green3")


> par(opar)

lets look at the some data

note that the pipe can be run in parts (short cut Ctrl+Shift+M, CMD+SHIFT+M )

Creating a ggplot

“ggplot” is part of the “tidyverse” and a widely used package to work with graphics note for ggplot there is “+” to combine commands, in contrast to “% > %” which is the pipe operator for commands outside ggplot

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

Create a ggplot with color = class

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, color = class))

Create a ggplot with size = cty

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, size = cty))

Create a ggplot with alpha = class

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, alpha = class))

Create a ggplot with shape = class

note there are only 6 different shapes, therefore “suv” has no shape and is not displayed

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, shape = class))

Create plot where property of geom is set manually

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy), color = "blue")

Recap

  • Where would you check for packages?
  • Where would you look on how to use packages?
  • When would you use size as function of a variable in a plot?

Facets

If there is a variable value which separates data it can be used to create multiple plots rather than multiple lines in one plot.

facet_wrap

facet_wrap wraps a 1d sequence of panels into 2d

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_wrap(~ class, nrow = 2)

facet_grid

facet_grid forms a matrix of panels defined by row and column facetting variables.

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_grid(drv ~ cyl)

Now get your hands dirty on size, color, shape, alpha

Analyse available data set in ggplot2! The data sets are listed and explained @ http://docs.ggplot2.org

Use - size - color - alpha - shape

to emphasise you message

midwest %>% distinct(state)
ggplot(midwest, aes(x= area, y = poptotal, color = percchildbelowpovert)) + geom_point() + facet_wrap(~ state, nrow = 3) + scale_y_log10() + scale_x_log10()

Geometic objects

different ways to present the same data

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) 

ggplot(data = mpg) + 
  geom_smooth(mapping = aes(x = displ, y = hwy))

geom_smooth with more than one line

draw a different line, with a different linetype, for each unique value of the variable that you map to linetype

ggplot(data = mpg) + 
  geom_smooth(mapping = aes(x = displ, y = hwy, linetype = drv, color = drv))

avoid the legend

ggplot(data = mpg) +
  geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))

display several geoms in same plot

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) +
  geom_smooth(mapping = aes(x = displ, y = hwy))

don’t repeat code

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point(mapping = aes(color = class)) + 
  geom_smooth()

use only subset of data for geom

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
geom_point(mapping = aes(color = class)) + 
  geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)

bar plot for discrete x-data

ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut))

lost in all the options?

Statistical transformations

box plot for discrete x- and continuous y-data

ggplot(data = diamonds) + 
  geom_boxplot(mapping = aes(x = cut, y = price, color = cut))

Violin plot for discrete x- and continuous y-data

gives good impression of distribution

ggplot(data = diamonds) + 
  geom_violin(mapping = aes(x = cut, y = price, color = cut))

Histogram

A histogram is a graphical representation of the distribution of numerical data.

https://de.wikipedia.org/wiki/Histogramm

ggplot(diamonds, aes(carat)) +
  geom_histogram()
# set binwidth
ggplot(diamonds, aes(carat)) +
  geom_histogram(binwidth = 0.01)
# set number of bins
ggplot(diamonds, aes(carat)) +
  geom_histogram(bins = 200)

use geom_freqpoly for easier comparison

# Rather than stacking histograms, it's easier to compare frequency
# polygons
ggplot(diamonds, aes(price, fill = cut)) +
  geom_histogram(binwidth = 500)
ggplot(diamonds, aes(price, colour = cut)) +
  geom_freqpoly(binwidth = 500)

work with densities, means each curve has area of one

# To make it easier to compare distributions with very different counts,
# put density on the y axis instead of the default count
ggplot(diamonds, aes(price, ..density.., colour = cut)) +
  geom_freqpoly(binwidth = 500)

Empirical Cumulative Distribution Function (ECDF)

The empirical distribution function estimates the cumulative distribution function underlying of the points in the sample and converges with probability 1

https://de.wikipedia.org/wiki/Empirische_Verteilungsfunktion

df <- data.frame(x = rnorm(10000))
ggplot(df, aes(x)) +
  geom_histogram()
ggplot(df, aes(x)) + stat_ecdf(geom = "step")

p  <- ggplot(df, aes(x)) + stat_ecdf()
pg <- ggplot_build(p)$data[[1]]
ggplot(pg, aes(x = x, y = 1-y )) + geom_step() + scale_y_log10() 

Find correlations

In statistics relationship between two variables.

library(corrplot)
cor_iris <- cor(iris %>% select(-Species))
corrplot.mixed(cor_iris)

further details on the corrplot package can be found in the vignette

vignette("corrplot-intro")

Maximal Information Coefficient (MIC)

In statistics, the maximal information coefficient (MIC) is a measure of the strength of the linear or non-linear association between two variables X and Y.

library(minerva)
compare_mic_r = function(x, y){
cat( "MIC:", mine(x,y)$MIC, ";", "correlation: ", cor(x,y), "\n")  
}
x <- runif(n=1000, min=0, max=1)
y2 <- 4*(x-0.5)^2; plot(sort(x),y2[order(x)],type="l"); compare_mic_r(x,y2)
MIC: 1 ; correlation:  -0.04411526 

y3 <- sin(6*pi*x*(1+x)); plot(sort(x),y3[order(x)],type="l"); compare_mic_r(x,y3)
MIC: 1 ; correlation:  -0.111569 

t <- seq(from=0,to=2*pi,length.out=1000)
x4 <- cos(t); y4 <- sin(t); plot(x4, y4, type="l",asp=1); compare_mic_r(x4,y4)
MIC: 0.6829015 ; correlation:  5.798018e-18 

Recap

  • Which geom seems useful for you?
  • Any idea where could use facet plots for one of your tasks?
  • What do you think about correlation and maximum information coefficient?

Now get your hands dirty on: facets, geom_ …

Explore a data set even further use

use

  • facets
  • different geoms_
  • geom_bar
  • geom_boxplot
  • geom_histogram
  • geom_freqpoly
  • stat_ecdf
  • correlation
  • MIC

need help?

Data wrangling

library(nycflights13)
flights

filter rows

filter all rows where month == 1 and day ==1, multiple filter conditions are separated by “,”

filter(flights, month == 1, day == 1)

store all x-mas flights

note, if you wrap the expression in () then the result will be displayed even when the result is assigned to a variable

(xmas_flights <- filter(flights, month == 12, day == 24))

boolean operators work as well

filter(flights, month == 11 | month == 12)

the following expressions give the same result

filter(flights, !(arr_delay > 120 | dep_delay > 120))
filter(flights, arr_delay <= 120, dep_delay <= 120)

Arrange rows with arrange()

arrange(flights, year, month, day)

select columns with select()

also an easy way to bring columns in a specific order

select all but a range of columns

select(flights, -(year:day))

more can be found in the cheatsheet

Add new variables with mutate()

note the %>% operator

select(flights, 
  year:day, 
  ends_with("delay"), 
  distance, 
  air_time) %>% 
mutate(
  gain = arr_delay - dep_delay,
  speed = distance / air_time * 60,
  hours = air_time / 60,
  gain_per_hour = gain / hours) %>% 
  select(-c(month, day, speed))

if you only want to keep the new columns use “transmute()”

select(flights, 
  year:day, 
  ends_with("delay"), 
  distance, 
  air_time) %>% 
transmute(
  gain = arr_delay - dep_delay,
  speed = distance / air_time * 60,
  hours = air_time / 60,
  gain_per_hour = gain / hours) 

Grouped summaries with summarise()

the mean of all depature delays

summarise(flights, delay = mean(dep_delay, na.rm = TRUE))
# na.rm a logical value indicating whether NA values should be stripped before the computation proceeds.
summarise(flights, delay = mean(dep_delay, na.rm = TRUE)) %>% as.numeric()
[1] 12.63907
by_day <- group_by(flights, year, month, day)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE))

find pattern of delays during the year

by_day <- flights %>% group_by(year, month)
summarise(by_day, delay = mean(dep_delay, na.rm = TRUE)) %>% ggplot(aes( x = month, y = delay, group = month)) +
  geom_col()

Find planes with high delays

not_cancelled <- flights %>% 
  filter(!is.na(arr_delay))

not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay)
  ) %>%
ggplot( mapping = aes(x = delay)) + 
  geom_freqpoly(binwidth = 10)

there seems a few planes with very high mean delay. Lets look closer into the issue

delays <- not_cancelled %>% 
  group_by(tailnum) %>% 
  summarise(
    delay = mean(arr_delay, na.rm = TRUE),
    n = n()
  )

ggplot(data = delays, mapping = aes(x = n, y = delay)) + 
  geom_point(alpha = 1/10)

the high delays are for tailnum wiht limited number of flight. Lets choose only tailnums where at least 25 flights are recorded

delays %>% 
  filter(n > 25) %>% 
  ggplot(mapping = aes(x = n, y = delay)) + 
    geom_point(alpha = 1/10)

what if we want to select the points under consideration not via a limit but from a plot? Use Shiny Gadgets

library(shiny)
library(miniUI)

ggbrush <- function(data, xvar, yvar) {
  
  ui <- miniPage(
    gadgetTitleBar("Drag to select points"),
    miniContentPanel(
      # The brush="brush" argument means we can listen for
      # brush events on the plot using input$brush.
      plotOutput("plot", height = "100%", brush = "brush")
    )
  )
  
  server <- function(input, output, session) {
    
    # Render the plot
    output$plot <- renderPlot({
      # Plot the data with x/y vars indicated by the caller.
      ggplot(data, aes_string(xvar, yvar)) + geom_point()
    })
    
    # Handle the Done button being pressed.
    observeEvent(input$done, {
      # Return the brushed points. See ?shiny::brushedPoints.
      stopApp(brushedPoints(data, input$brush, allRows = TRUE))
    })
  }
  
  runGadget(ui, server)
}
# pick_points(mtcars, ~wt, ~mpg)
brushed_points <- ggbrush(delays, "n", "delay")

brushed_points   %>% ggplot(mapping = aes(x = n, y = delay, color = selected_)) + 
    geom_point(alpha = 1/10)

brushed_points   %>% filter(selected_ ==TRUE)  %>%  ggplot(mapping = aes(x = n, y = delay, color = selected_)) + 
    geom_point(alpha = 1/3)

Now get your hands dirty on select, summarise, mutate…

now wrangle you data to analyse it use

  • summerise
  • mutate
  • select
  • group_by
  • filter

Now a few more things we need for the EuropeLeagueTransfers.Rmd

left_join

the data set nycflights13 has four tibbles (dataframes)

  • airlines
  • airports
  • planes
  • weather
 airlines
 airports
 planes
 weather

lets find out which manufacturer has the highest delays

first we need to join flights with planes

flight_planes <- left_join(flights, planes, by = "tailnum")

flight_planes %>% group_by(manufacturer) %>% summarise(delay_per_flight = sum(arr_delay, na.rm = TRUE)/ n(),number_of_flights = n()) %>% arrange(desc(delay_per_flight))

lets find out which airline has the highest delays

first we need to join flights with planes

flight_airlines <- left_join(flights, airlines)

flight_airlines %>% group_by(name) %>% summarise(delay_per_flight = sum(arr_delay, na.rm = TRUE)/ n(),number_of_flights = n()) %>% arrange(desc(delay_per_flight))

long and wide data.frames

for some operations the tidy wide format is not suitable as input to an operation, then a “long” version of the data.frame can be generated using the “melt” command.

A further example will be shown in EuropeLeagueTransfers.Rmd and further information on the topic can be found at http://seananderson.ca/2013/10/19/reshape.html

Cast functions cast (deutsch: gießen) a molten data frame into an array or data frame. is the reverse function of melt and will be used in EuropeLeagueTransfers.Rmd

last thing we need for EuropeLeagueTransfers.Rmd

grepl returns a logic vector given an expression

letters
grep("[a-c]", letters)
grep("[a-z]", letters)
grepl("[a-c]", letters)
grepl("[a-z]", letters)

Lets dive into some code

EuropeLeagueTransfers.Rmd

LS0tCnRpdGxlOiAiUiBLZW5udG5pc3NlIFZIUyAxXzIwMTciCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiAKClRyeSBleGVjdXRpbmcgdGhpcyBjaHVuayBieSBjbGlja2luZyB0aGUgKlJ1biogYnV0dG9uIHdpdGhpbiB0aGUgY2h1bmsgb3IgYnkgcGxhY2luZyB5b3VyIGN1cnNvciBpbnNpZGUgaXQgYW5kIHByZXNzaW5nICpDbWQrU2hpZnQrRW50ZXIqLiAKCgojIERhdGEgdmlzdWFsaXNhdGlvbgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCnRpZHl2ZXJzZV9wYWNrYWdlcygpICAjIHdoaWNoIHBhY2thZ2VzIGFyZSBpbiB0aWR5dmVyc2UKYGBgCgojIyBXaGVyZSBjYW4gSSBmaW5kIHVzZWZ1bCBwYWNrYWdlcz8KCi0gQ1JBTiAidGFzayB2aWV3cyIgIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnCi0gci1ibG9nZ2VycyBzZWFyY2ggaHR0cDovL3d3dy5yLWJsb2dnZXJzLmNvbSAKCiMjIFdoZXJlIGNhbiBJIGZpbmQgaG93IHRvIHVzZSBwYWNrYWdlcwoKLSBSZWZlcmVuY2UgbWFudWFsIG9uIENSQU4KLSBWaWduZXR0ZXMKLSA/Ci0gRGVtb3MKCgpgYGB7cn0KIyBMaXN0IHZpZ25ldHRlcyBmcm9tIGFsbCAqYXR0YWNoZWQqIHBhY2thZ2VzCnZpZ25ldHRlKGFsbCA9IEZBTFNFKQojIExpc3QgdmlnbmV0dGVzIGZyb20gYWxsICppbnN0YWxsZWQqIHBhY2thZ2VzIChjYW4gdGFrZSBhIGxvbmcgdGltZSEpOgp2aWduZXR0ZShhbGwgPSBUUlVFKQojIGZpbmQgdmlnbmV0dGVzIG9mICJnZ3Bsb3QyIgp2aWduZXR0ZShwYWNrYWdlID0gImdncGxvdDIiKQojIHZpZXcgdmlnbmV0dGUgImdncGxvdDItc3BlY3MiICAKdmlnbmV0dGUoImdncGxvdDItc3BlY3MiKQpgYGAKCgpub3cgbG9vayBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiBnZ3Bsb3QKCmBgYHtyfQo/Z2dwbG90MgpkZW1vKCkgICAgICAgICAgIyBmaW5kIGRlbW9zIGZvciBhdHRhY2hlZCBwYWNrYWdlcwpkZW1vKGdyYXBoaWNzKSAgIyBBIHNob3cgb2Ygc29tZSBvZiBSJ3MgZ3JhcGhpY3MgY2FwYWJpbGl0aWVzLCBydW4gaW4gY29uc29sZQoKYGBgCgoKIyMgbGV0cyBsb29rIGF0IHRoZSBzb21lIGRhdGEKCm5vdGUgdGhhdCB0aGUgcGlwZSBjYW4gYmUgcnVuIGluIHBhcnRzIChzaG9ydCBjdXQgQ3RybCtTaGlmdCtNLCBDTUQrU0hJRlQrTSApCgpgYGB7cn0KbXBnICAlPiUgc2VsZWN0KGRpc3BsLCBjdHksIGh3eSwgeWVhcikgICU+JSBwbG90KCkKIyBjb21wYXJlIHRoaXMgdG8gdGhlICJuZXN0ZWQiIHZlcnNpb24gCnBsb3Qoc2VsZWN0KG1wZyxkaXNwbCxjdHksaHd5LHllYXIpKQpgYGAKCgoKIyMgQ3JlYXRpbmcgYSBnZ3Bsb3QKCiJnZ3Bsb3QiIGlzIHBhcnQgb2YgdGhlICJ0aWR5dmVyc2UiIGFuZCBhIHdpZGVseSB1c2VkIHBhY2thZ2UgdG8gd29yayB3aXRoIGdyYXBoaWNzIAoqKm5vdGUqKiBmb3IgZ2dwbG90IHRoZXJlIGlzICIrIiB0byBjb21iaW5lIGNvbW1hbmRzLCBpbiBjb250cmFzdCB0byAiJSA+ICUiIHdoaWNoIGlzIHRoZSBwaXBlIG9wZXJhdG9yIGZvciBjb21tYW5kcyBvdXRzaWRlIGdncGxvdAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkKYGBgCgoKIyMjIENyZWF0ZSBhIGdncGxvdCB3aXRoIGNvbG9yID0gY2xhc3MKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvciA9IGNsYXNzKSkKYGBgCgojIyMgQ3JlYXRlIGEgZ2dwbG90IHdpdGggc2l6ZSA9IGN0eQoKYGBge3J9CmdncGxvdChkYXRhID0gbXBnKSArIAogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIHNpemUgPSBjdHkpKQpgYGAKCiMjIyBDcmVhdGUgYSBnZ3Bsb3Qgd2l0aCBhbHBoYSA9IGNsYXNzCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgYWxwaGEgPSBjbGFzcykpCmBgYAoKIyMjIENyZWF0ZSBhIGdncGxvdCB3aXRoIHNoYXBlID0gY2xhc3MKCioqbm90ZSoqIHRoZXJlIGFyZSBvbmx5IDYgZGlmZmVyZW50IHNoYXBlcywgdGhlcmVmb3JlICJzdXYiIGhhcyBubyBzaGFwZSBhbmQgaXMgbm90IGRpc3BsYXllZAoKYGBge3J9CmdncGxvdChkYXRhID0gbXBnKSArIAogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIHNoYXBlID0gY2xhc3MpKQpgYGAKCgoKIyMjIENyZWF0ZSBwbG90IHdoZXJlIHByb3BlcnR5IG9mIGdlb20gaXMgc2V0IG1hbnVhbGx5CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSksIGNvbG9yID0gImJsdWUiKQpgYGAKCiMjIFJlY2FwCi0gV2hlcmUgd291bGQgeW91IGNoZWNrIGZvciBwYWNrYWdlcz8KLSBXaGVyZSB3b3VsZCB5b3UgbG9vayBvbiBob3cgdG8gdXNlIHBhY2thZ2VzPwotIFdoZW4gd291bGQgeW91IHVzZSBzaXplIGFzIGZ1bmN0aW9uIG9mIGEgdmFyaWFibGUgaW4gYSBwbG90PwoKCiMjIEZhY2V0cwpJZiB0aGVyZSBpcyBhIHZhcmlhYmxlIHZhbHVlIHdoaWNoIHNlcGFyYXRlcyBkYXRhIGl0IGNhbiBiZSB1c2VkIHRvIGNyZWF0ZSBtdWx0aXBsZSBwbG90cyByYXRoZXIgdGhhbiBtdWx0aXBsZSBsaW5lcyBpbiBvbmUgcGxvdC4KCiMjIyBmYWNldF93cmFwCmZhY2V0X3dyYXAgd3JhcHMgYSAxZCBzZXF1ZW5jZSBvZiBwYW5lbHMgaW50byAyZAoKYGBge3J9CmdncGxvdChkYXRhID0gbXBnKSArIAogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKSArIAogIGZhY2V0X3dyYXAofiBjbGFzcywgbnJvdyA9IDIpCmBgYAoKCiMjIyBmYWNldF9ncmlkCmZhY2V0X2dyaWQgZm9ybXMgYSBtYXRyaXggb2YgcGFuZWxzIGRlZmluZWQgYnkgcm93IGFuZCBjb2x1bW4gZmFjZXR0aW5nIHZhcmlhYmxlcy4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyAKICBmYWNldF9ncmlkKGRydiB+IGN5bCkKYGBgCgoKIyMjIyBOb3cgZ2V0IHlvdXIgaGFuZHMgZGlydHkgb24gc2l6ZSwgY29sb3IsIHNoYXBlLCBhbHBoYQoKQW5hbHlzZSBhdmFpbGFibGUgZGF0YSBzZXQgaW4gZ2dwbG90MiEgVGhlIGRhdGEgc2V0cyBhcmUgbGlzdGVkIGFuZCBleHBsYWluZWQgQCBodHRwOi8vZG9jcy5nZ3Bsb3QyLm9yZwoKVXNlCi0gc2l6ZQotIGNvbG9yCi0gYWxwaGEKLSBzaGFwZQoKdG8gZW1waGFzaXNlIHlvdSBtZXNzYWdlCgoKYGBge3J9CmdncGxvdChtaWR3ZXN0LCBhZXMoeD0gYXJlYSwgeSA9IHBvcHRvdGFsLCBjb2xvciA9IHBlcmNjaGlsZGJlbG93cG92ZXJ0KSkgKyBnZW9tX3BvaW50KCkgKyBmYWNldF93cmFwKH4gc3RhdGUsIG5yb3cgPSAzKSArIHNjYWxlX3lfbG9nMTAoKSArIHNjYWxlX3hfbG9nMTAoKQpgYGAKCiMjIEdlb21ldGljIG9iamVjdHMKZGlmZmVyZW50IHdheXMgdG8gcHJlc2VudCB0aGUgc2FtZSBkYXRhCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpIApgYGAKCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9zbW9vdGgobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKQpgYGAKCiMjIyBnZW9tX3Ntb290aCB3aXRoIG1vcmUgdGhhbiBvbmUgbGluZQpkcmF3IGEgZGlmZmVyZW50IGxpbmUsIHdpdGggYSBkaWZmZXJlbnQgbGluZXR5cGUsIGZvciBlYWNoIHVuaXF1ZSB2YWx1ZSBvZiB0aGUgdmFyaWFibGUgdGhhdCB5b3UgbWFwIHRvIGxpbmV0eXBlCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyAKICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgbGluZXR5cGUgPSBkcnYsIGNvbG9yID0gZHJ2KSkKYGBgCgojIyMgYXZvaWQgdGhlIGxlZ2VuZAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZykgKwogIGdlb21fc21vb3RoKG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBncm91cCA9IGRydikpCmBgYAoKCiMjIyBkaXNwbGF5IHNldmVyYWwgZ2VvbXMgaW4gc2FtZSBwbG90CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcpICsgCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsKICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpCmBgYAoKCiMjIyBkb24ndCByZXBlYXQgY29kZSAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKSArIAogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvciA9IGNsYXNzKSkgKyAKICBnZW9tX3Ntb290aCgpCmBgYAoKCiMjIyB1c2Ugb25seSBzdWJzZXQgb2YgZGF0YSBmb3IgZ2VvbQoKYGBge3J9CmdncGxvdChkYXRhID0gbXBnLCBtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsgCmdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvciA9IGNsYXNzKSkgKyAKICBnZW9tX3Ntb290aChkYXRhID0gZmlsdGVyKG1wZywgY2xhc3MgPT0gInN1YmNvbXBhY3QiKSwgc2UgPSBGQUxTRSkKYGBgCgoKIyMjIGJhciBwbG90IGZvciBkaXNjcmV0ZSB4LWRhdGEKYGBge3J9CmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsgCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gY3V0KSkKYGBgCgoKCiMjIyBsb3N0IGluIGFsbCB0aGUgb3B0aW9ucz8KLSBDSEVBVCBTSEVFVFMgYXJlIGF0IHlvdXIgZmluZ2VydGlwcyB1bmRlciBIRUxQIG1lbnUgb2YgUlN0dWRpbyBJREUgb3IKaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLyAKCgotIHN0YWNrb3ZlcmZsb3cgaXMgYSB2aXZpZCBjb21tdW5pdHkgCmh0dHA6Ly9zdGFja292ZXJmbG93LmNvbSAKCi0gUkRvY3VtZW50YXRpb24gc2VhcmNoZXMgQ1JBTiwgQmlvQ29uZHVjdG9yIGFuZCBHaXRodWIgcGFja2FnZXMgaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnCgoKIyBTdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbnMKCiMjIGJveCBwbG90IGZvciBkaXNjcmV0ZSB4LSBhbmQgY29udGludW91cyB5LWRhdGEKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzKSArIAogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBjdXQsIHkgPSBwcmljZSkpCmBgYAoKCiMjIFZpb2xpbiBwbG90IGZvciBkaXNjcmV0ZSB4LSBhbmQgY29udGludW91cyB5LWRhdGEKZ2l2ZXMgZ29vZCBpbXByZXNzaW9uIG9mIGRpc3RyaWJ1dGlvbgoKYGBge3J9CmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsgCiAgZ2VvbV92aW9saW4obWFwcGluZyA9IGFlcyh4ID0gY3V0LCB5ID0gcHJpY2UsIGNvbG9yID0gY3V0KSkKYGBgCgojIyBIaXN0b2dyYW0KQSBoaXN0b2dyYW0gaXMgYSBncmFwaGljYWwgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBudW1lcmljYWwgZGF0YS4KCmh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL0hpc3RvZ3JhbW0KCmBgYHtyfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyhjYXJhdCkpICsKICBnZW9tX2hpc3RvZ3JhbSgpCiMgc2V0IGJpbndpZHRoCmdncGxvdChkaWFtb25kcywgYWVzKGNhcmF0KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkKIyBzZXQgbnVtYmVyIG9mIGJpbnMKZ2dwbG90KGRpYW1vbmRzLCBhZXMoY2FyYXQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwMCkKYGBgCgojIyB1c2UgZ2VvbV9mcmVxcG9seSBmb3IgZWFzaWVyIGNvbXBhcmlzb24KCmBgYHtyfQojIFJhdGhlciB0aGFuIHN0YWNraW5nIGhpc3RvZ3JhbXMsIGl0J3MgZWFzaWVyIHRvIGNvbXBhcmUgZnJlcXVlbmN5CiMgcG9seWdvbnMKZ2dwbG90KGRpYW1vbmRzLCBhZXMocHJpY2UsIGZpbGwgPSBjdXQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1MDApCmdncGxvdChkaWFtb25kcywgYWVzKHByaWNlLCBjb2xvdXIgPSBjdXQpKSArCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDUwMCkKYGBgCgoKd29yayB3aXRoIGRlbnNpdGllcywgbWVhbnMgZWFjaCBjdXJ2ZSBoYXMgYXJlYSBvZiBvbmUKCmBgYHtyfQojIFRvIG1ha2UgaXQgZWFzaWVyIHRvIGNvbXBhcmUgZGlzdHJpYnV0aW9ucyB3aXRoIHZlcnkgZGlmZmVyZW50IGNvdW50cywKIyBwdXQgZGVuc2l0eSBvbiB0aGUgeSBheGlzIGluc3RlYWQgb2YgdGhlIGRlZmF1bHQgY291bnQKZ2dwbG90KGRpYW1vbmRzLCBhZXMocHJpY2UsIC4uZGVuc2l0eS4uLCBjb2xvdXIgPSBjdXQpKSArCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aCA9IDUwMCkKYGBgCgojIyBFbXBpcmljYWwgQ3VtdWxhdGl2ZSBEaXN0cmlidXRpb24gRnVuY3Rpb24gKEVDREYpCgpUaGUgZW1waXJpY2FsIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBlc3RpbWF0ZXMgdGhlIGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uIHVuZGVybHlpbmcgb2YgdGhlIHBvaW50cyBpbiB0aGUgc2FtcGxlIGFuZCBjb252ZXJnZXMgd2l0aCBwcm9iYWJpbGl0eSAxCgpodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9FbXBpcmlzY2hlX1ZlcnRlaWx1bmdzZnVua3Rpb24KCgpgYGB7cn0KZGYgPC0gZGF0YS5mcmFtZSh4ID0gcm5vcm0oMTAwMDApKQpnZ3Bsb3QoZGYsIGFlcyh4KSkgKwogIGdlb21faGlzdG9ncmFtKCkKZ2dwbG90KGRmLCBhZXMoeCkpICsgc3RhdF9lY2RmKGdlb20gPSAic3RlcCIpCgpwICA8LSBnZ3Bsb3QoZGYsIGFlcyh4KSkgKyBzdGF0X2VjZGYoKQpwZyA8LSBnZ3Bsb3RfYnVpbGQocCkkZGF0YVtbMV1dCmdncGxvdChwZywgYWVzKHggPSB4LCB5ID0gMS15ICkpICsgZ2VvbV9zdGVwKCkgKyBzY2FsZV95X2xvZzEwKCkgCgoKCmBgYAoKIyMgRmluZCBjb3JyZWxhdGlvbnMKSW4gc3RhdGlzdGljcyByZWxhdGlvbnNoaXAgYmV0d2VlbiB0d28gdmFyaWFibGVzLgoKYGBge3J9CmxpYnJhcnkoY29ycnBsb3QpCmNvcl9pcmlzIDwtIGNvcihpcmlzICU+JSBzZWxlY3QoLVNwZWNpZXMpKQpjb3JycGxvdC5taXhlZChjb3JfaXJpcykKCmBgYAoKZnVydGhlciBkZXRhaWxzIG9uIHRoZSBjb3JycGxvdCBwYWNrYWdlIGNhbiBiZSBmb3VuZCBpbiB0aGUgdmlnbmV0dGUKCmBgYHtyfQp2aWduZXR0ZSgiY29ycnBsb3QtaW50cm8iKQpgYGAKCiMjIE1heGltYWwgSW5mb3JtYXRpb24gQ29lZmZpY2llbnQgKE1JQykKCkluIHN0YXRpc3RpY3MsIHRoZSBtYXhpbWFsIGluZm9ybWF0aW9uIGNvZWZmaWNpZW50IChNSUMpIGlzIGEgbWVhc3VyZSBvZiB0aGUgc3RyZW5ndGggb2YgdGhlIGxpbmVhciBvciAqKm5vbi1saW5lYXIqKiBhc3NvY2lhdGlvbiBiZXR3ZWVuIHR3byB2YXJpYWJsZXMgWCBhbmQgWS4KCmBgYHtyfQoKbGlicmFyeShtaW5lcnZhKQpjb21wYXJlX21pY19yID0gZnVuY3Rpb24oeCwgeSl7CmNhdCggIk1JQzoiLCBtaW5lKHgseSkkTUlDLCAiOyIsICJjb3JyZWxhdGlvbjogIiwgY29yKHgseSksICJcbiIpICAKfQp4IDwtIHJ1bmlmKG49MTAwMCwgbWluPTAsIG1heD0xKQp5MiA8LSA0Kih4LTAuNSleMjsgcGxvdChzb3J0KHgpLHkyW29yZGVyKHgpXSx0eXBlPSJsIik7IGNvbXBhcmVfbWljX3IoeCx5MikKeTMgPC0gc2luKDYqcGkqeCooMSt4KSk7IHBsb3Qoc29ydCh4KSx5M1tvcmRlcih4KV0sdHlwZT0ibCIpOyBjb21wYXJlX21pY19yKHgseTMpCnQgPC0gc2VxKGZyb209MCx0bz0yKnBpLGxlbmd0aC5vdXQ9MTAwMCkKeDQgPC0gY29zKHQpOyB5NCA8LSBzaW4odCk7IHBsb3QoeDQsIHk0LCB0eXBlPSJsIixhc3A9MSk7IGNvbXBhcmVfbWljX3IoeDQseTQpCgpgYGAKCgoKIyMgUmVjYXAKCi0gV2hpY2ggZ2VvbSBzZWVtcyB1c2VmdWwgZm9yIHlvdT8KLSBBbnkgaWRlYSB3aGVyZSBjb3VsZCB1c2UgZmFjZXQgcGxvdHMgZm9yIG9uZSBvZiB5b3VyIHRhc2tzPwotIFdoYXQgZG8geW91IHRoaW5rIGFib3V0IGNvcnJlbGF0aW9uIGFuZCBtYXhpbXVtIGluZm9ybWF0aW9uIGNvZWZmaWNpZW50PwoKCiMjIyMgTm93IGdldCB5b3VyIGhhbmRzIGRpcnR5IG9uOiBmYWNldHMsIGdlb21fIC4uLgoKRXhwbG9yZSBhIGRhdGEgc2V0IGV2ZW4gZnVydGhlciB1c2UKCnVzZSAKCi0gZmFjZXRzCi0gZGlmZmVyZW50IGdlb21zXwogIC0gZ2VvbV9iYXIKICAtIGdlb21fYm94cGxvdAogIC0gZ2VvbV9oaXN0b2dyYW0KICAtIGdlb21fZnJlcXBvbHkKICAtIHN0YXRfZWNkZgotIGNvcnJlbGF0aW9uCi0gTUlDCgpuZWVkIGhlbHA/CgotIGNoZWF0IHNoZWV0cwotIGRvY3Mgb2YgZ2dwbG90Ci0gaHR0cDovL2RvY3MuZ2dwbG90Mi5vcmcKCgoKIyAgRGF0YSB3cmFuZ2xpbmcKCmBgYHtyfQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykKZmxpZ2h0cwpgYGAKCgojIyBmaWx0ZXIgcm93cwoKZmlsdGVyIGFsbCByb3dzIHdoZXJlIG1vbnRoID09IDEgYW5kIGRheSA9PTEsIG11bHRpcGxlIGZpbHRlciBjb25kaXRpb25zIGFyZSBzZXBhcmF0ZWQgYnkgIiwiCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQpgYGAKCgojIyBzdG9yZSBhbGwgeC1tYXMgZmxpZ2h0cwoKbm90ZSwgaWYgeW91IHdyYXAgdGhlIGV4cHJlc3Npb24gaW4gKCkgdGhlbiB0aGUgcmVzdWx0IHdpbGwgYmUgZGlzcGxheWVkIGV2ZW4gd2hlbiB0aGUgcmVzdWx0IGlzIGFzc2lnbmVkIHRvIGEgdmFyaWFibGUKCmBgYHtyfQooeG1hc19mbGlnaHRzIDwtIGZpbHRlcihmbGlnaHRzLCBtb250aCA9PSAxMiwgZGF5ID09IDI0KSkKYGBgCgoKIyMgYm9vbGVhbiBvcGVyYXRvcnMgd29yayBhcyB3ZWxsCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDExIHwgbW9udGggPT0gMTIpCmBgYAoKCnRoZSBmb2xsb3dpbmcgZXhwcmVzc2lvbnMgZ2l2ZSB0aGUgc2FtZSByZXN1bHQKCgpgYGB7cn0KZmlsdGVyKGZsaWdodHMsICEoYXJyX2RlbGF5ID4gMTIwIHwgZGVwX2RlbGF5ID4gMTIwKSkKZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheSA8PSAxMjAsIGRlcF9kZWxheSA8PSAxMjApCmBgYAoKCiMjIEFycmFuZ2Ugcm93cyB3aXRoIGFycmFuZ2UoKQoKCmBgYHtyfQphcnJhbmdlKGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCmBgYAoKIyMgc2VsZWN0IGNvbHVtbnMgd2l0aCBzZWxlY3QoKQphbHNvIGFuIGVhc3kgd2F5IHRvIGJyaW5nIGNvbHVtbnMgaW4gYSBzcGVjaWZpYyBvcmRlcgoKYGBge3J9CnNlbGVjdChmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQpgYGAKc2VsZWN0IGFsbCBidXQgYSByYW5nZSBvZiBjb2x1bW5zCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIC0oeWVhcjpkYXkpKQpgYGAKCm1vcmUgY2FuIGJlIGZvdW5kIGluIHRoZSBjaGVhdHNoZWV0IAoKIyMgQWRkIG5ldyB2YXJpYWJsZXMgd2l0aCBtdXRhdGUoKQoKbm90ZSB0aGUgJT4lIG9wZXJhdG9yCgpgYGB7cn0Kc2VsZWN0KGZsaWdodHMsIAogIHllYXI6ZGF5LCAKICBlbmRzX3dpdGgoImRlbGF5IiksIAogIGRpc3RhbmNlLCAKICBhaXJfdGltZSkgJT4lIAptdXRhdGUoCiAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwKICBzcGVlZCA9IGRpc3RhbmNlIC8gYWlyX3RpbWUgKiA2MCwKICBob3VycyA9IGFpcl90aW1lIC8gNjAsCiAgZ2Fpbl9wZXJfaG91ciA9IGdhaW4gLyBob3VycykgJT4lIAogIHNlbGVjdCgtYyhtb250aCwgZGF5LCBzcGVlZCkpCmBgYAoKaWYgeW91IG9ubHkgd2FudCB0byBrZWVwIHRoZSBuZXcgY29sdW1ucyB1c2UgInRyYW5zbXV0ZSgpIgoKYGBge3J9CnNlbGVjdChmbGlnaHRzLCAKICB5ZWFyOmRheSwgCiAgZW5kc193aXRoKCJkZWxheSIpLCAKICBkaXN0YW5jZSwgCiAgYWlyX3RpbWUpICU+JSAKdHJhbnNtdXRlKAogIGdhaW4gPSBhcnJfZGVsYXkgLSBkZXBfZGVsYXksCiAgc3BlZWQgPSBkaXN0YW5jZSAvIGFpcl90aW1lICogNjAsCiAgaG91cnMgPSBhaXJfdGltZSAvIDYwLAogIGdhaW5fcGVyX2hvdXIgPSBnYWluIC8gaG91cnMpIApgYGAKCgojIyBHcm91cGVkIHN1bW1hcmllcyB3aXRoIHN1bW1hcmlzZSgpCgp0aGUgbWVhbiBvZiBhbGwgZGVwYXR1cmUgZGVsYXlzCgpgYGB7cn0Kc3VtbWFyaXNlKGZsaWdodHMsIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpCgojIG5hLnJtCWEgbG9naWNhbCB2YWx1ZSBpbmRpY2F0aW5nIHdoZXRoZXIgTkEgdmFsdWVzIHNob3VsZCBiZSBzdHJpcHBlZCBiZWZvcmUgdGhlIGNvbXB1dGF0aW9uIHByb2NlZWRzLgoKc3VtbWFyaXNlKGZsaWdodHMsIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpICU+JSBhcy5udW1lcmljKCkKCmBgYAoKCgpgYGB7cn0KYnlfZGF5IDwtIGdyb3VwX2J5KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpCnN1bW1hcmlzZShieV9kYXksIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpCmBgYAoKZmluZCBwYXR0ZXJuIG9mIGRlbGF5cyBkdXJpbmcgdGhlIHllYXIKCmBgYHtyfQpieV9kYXkgPC0gZmxpZ2h0cyAlPiUgZ3JvdXBfYnkoeWVhciwgbW9udGgpCnN1bW1hcmlzZShieV9kYXksIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpICU+JSBnZ3Bsb3QoYWVzKCB4ID0gbW9udGgsIHkgPSBkZWxheSwgZ3JvdXAgPSBtb250aCkpICsKICBnZW9tX2NvbCgpCmBgYAoKCgojIyBGaW5kIHBsYW5lcyB3aXRoIGhpZ2ggZGVsYXlzCgpgYGB7cn0Kbm90X2NhbmNlbGxlZCA8LSBmbGlnaHRzICU+JSAKICBmaWx0ZXIoIWlzLm5hKGFycl9kZWxheSkpCgpub3RfY2FuY2VsbGVkICU+JSAKICBncm91cF9ieSh0YWlsbnVtKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSkKICApICU+JQpnZ3Bsb3QoIG1hcHBpbmcgPSBhZXMoeCA9IGRlbGF5KSkgKyAKICBnZW9tX2ZyZXFwb2x5KGJpbndpZHRoID0gMTApCmBgYAoKdGhlcmUgc2VlbXMgYSBmZXcgcGxhbmVzIHdpdGggdmVyeSBoaWdoIG1lYW4gZGVsYXkuIExldHMgbG9vayBjbG9zZXIgaW50byB0aGUgaXNzdWUKCmBgYHtyfQpkZWxheXMgPC0gbm90X2NhbmNlbGxlZCAlPiUgCiAgZ3JvdXBfYnkodGFpbG51bSkgJT4lIAogIHN1bW1hcmlzZSgKICAgIGRlbGF5ID0gbWVhbihhcnJfZGVsYXksIG5hLnJtID0gVFJVRSksCiAgICBuID0gbigpCiAgKQoKZ2dwbG90KGRhdGEgPSBkZWxheXMsIG1hcHBpbmcgPSBhZXMoeCA9IG4sIHkgPSBkZWxheSkpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDEvMTApCmBgYAoKCnRoZSBoaWdoIGRlbGF5cyBhcmUgZm9yIHRhaWxudW0gd2lodCBsaW1pdGVkIG51bWJlciBvZiBmbGlnaHQuCkxldHMgY2hvb3NlIG9ubHkgdGFpbG51bXMgd2hlcmUgYXQgbGVhc3QgMjUgZmxpZ2h0cyBhcmUgcmVjb3JkZWQKCmBgYHtyfQpkZWxheXMgJT4lIAogIGZpbHRlcihuID4gMjUpICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gbiwgeSA9IGRlbGF5KSkgKyAKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLzEwKQpgYGAKCndoYXQgaWYgd2Ugd2FudCB0byBzZWxlY3QgdGhlIHBvaW50cyB1bmRlciBjb25zaWRlcmF0aW9uIG5vdCB2aWEgYSBsaW1pdCBidXQgZnJvbSBhIHBsb3Q/IFVzZSAqKlNoaW55IEdhZGdldHMqKgoKYGBge3J9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkobWluaVVJKQoKZ2dicnVzaCA8LSBmdW5jdGlvbihkYXRhLCB4dmFyLCB5dmFyKSB7CiAgCiAgdWkgPC0gbWluaVBhZ2UoCiAgICBnYWRnZXRUaXRsZUJhcigiRHJhZyB0byBzZWxlY3QgcG9pbnRzIiksCiAgICBtaW5pQ29udGVudFBhbmVsKAogICAgICAjIFRoZSBicnVzaD0iYnJ1c2giIGFyZ3VtZW50IG1lYW5zIHdlIGNhbiBsaXN0ZW4gZm9yCiAgICAgICMgYnJ1c2ggZXZlbnRzIG9uIHRoZSBwbG90IHVzaW5nIGlucHV0JGJydXNoLgogICAgICBwbG90T3V0cHV0KCJwbG90IiwgaGVpZ2h0ID0gIjEwMCUiLCBicnVzaCA9ICJicnVzaCIpCiAgICApCiAgKQogIAogIHNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgICAKICAgICMgUmVuZGVyIHRoZSBwbG90CiAgICBvdXRwdXQkcGxvdCA8LSByZW5kZXJQbG90KHsKICAgICAgIyBQbG90IHRoZSBkYXRhIHdpdGggeC95IHZhcnMgaW5kaWNhdGVkIGJ5IHRoZSBjYWxsZXIuCiAgICAgIGdncGxvdChkYXRhLCBhZXNfc3RyaW5nKHh2YXIsIHl2YXIpKSArIGdlb21fcG9pbnQoKQogICAgfSkKICAgIAogICAgIyBIYW5kbGUgdGhlIERvbmUgYnV0dG9uIGJlaW5nIHByZXNzZWQuCiAgICBvYnNlcnZlRXZlbnQoaW5wdXQkZG9uZSwgewogICAgICAjIFJldHVybiB0aGUgYnJ1c2hlZCBwb2ludHMuIFNlZSA/c2hpbnk6OmJydXNoZWRQb2ludHMuCiAgICAgIHN0b3BBcHAoYnJ1c2hlZFBvaW50cyhkYXRhLCBpbnB1dCRicnVzaCwgYWxsUm93cyA9IFRSVUUpKQogICAgfSkKICB9CiAgCiAgcnVuR2FkZ2V0KHVpLCBzZXJ2ZXIpCn0KIyBwaWNrX3BvaW50cyhtdGNhcnMsIH53dCwgfm1wZykKYnJ1c2hlZF9wb2ludHMgPC0gZ2dicnVzaChkZWxheXMsICJuIiwgImRlbGF5IikKCmJydXNoZWRfcG9pbnRzICAgJT4lIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZGVsYXksIGNvbG9yID0gc2VsZWN0ZWRfKSkgKyAKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLzEwKQoKYnJ1c2hlZF9wb2ludHMgICAlPiUgZmlsdGVyKHNlbGVjdGVkXyA9PVRSVUUpICAlPiUgIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBuLCB5ID0gZGVsYXksIGNvbG9yID0gc2VsZWN0ZWRfKSkgKyAKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAxLzMpCgpgYGAKCgojIyMjIE5vdyBnZXQgeW91ciBoYW5kcyBkaXJ0eSBvbiBzZWxlY3QsIHN1bW1hcmlzZSwgbXV0YXRlLi4uCgpub3cgd3JhbmdsZSB5b3UgZGF0YSB0byBhbmFseXNlIGl0IHVzZQoKLSBzdW1tZXJpc2UKLSBtdXRhdGUKLSBzZWxlY3QKLSBncm91cF9ieQotIGZpbHRlcgoKCiMgTm93IGEgZmV3IG1vcmUgdGhpbmdzIHdlIG5lZWQgZm9yIHRoZSBFdXJvcGVMZWFndWVUcmFuc2ZlcnMuUm1kCgojIyBsZWZ0X2pvaW4KCnRoZSBkYXRhIHNldCBueWNmbGlnaHRzMTMgaGFzIGZvdXIgdGliYmxlcyAoZGF0YWZyYW1lcykKCi0gYWlybGluZXMKLSBhaXJwb3J0cwotIHBsYW5lcwotIHdlYXRoZXIKCgpgYGB7cn0KIGFpcmxpbmVzCiBhaXJwb3J0cwogcGxhbmVzCiB3ZWF0aGVyCmBgYAoKCiMjIGZpbmQgdGhlIGxpbmtzIGJldHdlZW4gdGhlIGRhdGEuZnJhbWVzCgoKYGBge3J9CmxpYnJhcnkodmlzTmV0d29yaykKIyB0aGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYSBkYXRhLmZyYW1lIHdpdGggdGhlIG5hbWUgb2YgdGhlIGRhdGEuZnJhbWUgYW5kIHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucyBvZiB0aGF0IGRhdGEuZnJhbWUKY3JlYXRlX2RmX29mX25hbWVzID0gZnVuY3Rpb24oZGYsIG5hbWUpewogIGRhdGEuZnJhbWUoZnJvbSA9IG5hbWUsIHRvID0gbmFtZXMoZGYpKQp9CgojIGNyZWF0ZSBhIG5hbWVzIGxpc3Qgb2YgdGhlIGRhdGEuZnJhbWVzCmxpc3Rfb2ZfZGYgPC0gbGlzdChmbGlnaHRzID0gZmxpZ2h0cyxhaXJsaW5lcyA9IGFpcmxpbmVzLCBhaXJwb3J0cyA9IGFpcnBvcnRzLCB3ZWF0aGVyID0gd2VhdGhlciwgcGxhbmVzID0gcGxhbmVzKSAKIyBhbmQgbWFwIHRoZW0gdG8gYnVpbGQgb25lIGRhdGEuZnJhbWUgd2l0aCB0d28gY29sdW1ucwojIC0gZnJvbSBjb250YWlucyBhbGwgIGRhdGEuZnJhbWUgbmFtZXMKIyAtIHRvICBjb250YWlucyBhbGwgY29sdW1uIG5hbWVzCmVkZ2UgPC0gbWFwMl9kZihsaXN0X29mX2RmLG5hbWVzKGxpc3Rfb2ZfZGYpLCBjcmVhdGVfZGZfb2ZfbmFtZXMpCgojIGNyZWF0ZSBhIHZpc05ldHdvcmsKCm5vZGVzRnJvbSA8LSAgZWRnZSAlPiUgY2JpbmQodW5saXN0KC4kZnJvbSksIlRhYmxlIikgJT4lIHNlbGVjdCgzLDQpICU+JSBkYXRhLmZyYW1lICAKbm9kZXNUbyA8LSAgZWRnZSAlPiUgY2JpbmQodW5saXN0KC4kdG8pLCJBdHRyaWJ1dGUiKSAlPiUgc2VsZWN0KDMsNCkgJT4lIGRhdGEuZnJhbWUgCgpuYW1lcyhub2Rlc0Zyb20pIDwtIGMoImlkIiwgImdyb3VwIikKbmFtZXMobm9kZXNUbykgPC0gYygiaWQiLCAiZ3JvdXAiKQoKbm9kZXMgPC0gcmJpbmQobm9kZXNGcm9tLG5vZGVzVG8pICU+JSB1bmlxdWUoKSAKbm9kZXMkaWQgPC0gYXMuY2hhcmFjdGVyKChub2RlcyRpZCkpICAKbm9kZXMgPC0gbm9kZXMgJT4lIHVuaXF1ZSgpICU+JSBhcnJhbmdlKGlkKQp2aXNOZXR3b3JrKG5vZGVzLCBlZGdlKSU+JQogIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIGRlZ3JlZSA9IDIpLCBub2Rlc0lkU2VsZWN0aW9uID0gVFJVRSkgJT4lCiAgdmlzRWRnZXMoYXJyb3dzID0gInRvIikgJT4lICAKICB2aXNHcm91cHMoZ3JvdXBuYW1lID0gIlRhYmxlIiwgICAgIHNoYXBlID0gImljb24iLCBpY29uID0gbGlzdChjb2RlID0gImYxMTQiLCBjb2xvciA9ICJncmVlbiIsc2l6ZSA9IDc1KSkgJT4lCiAgdmlzR3JvdXBzKGdyb3VwbmFtZSA9ICJBdHRyaWJ1dGUiLCBzaGFwZSA9ICJpY29uIiwgaWNvbiA9IGxpc3QoY29kZSA9ICJmMTE1IiwgY29sb3IgPSAibGlnaHRncmVlbiIsIHNpemUgPSA0NSkpICU+JQogIGFkZEZvbnRBd2Vzb21lKCkgCiMgbGlzdCBvZiBpY29ucyBodHRwOi8vYXN0cm9uYXV0d2ViLmNvL3NuaXBwZXQvZm9udC1hd2Vzb21lLwoKYGBgCgojIyBsZXRzIGZpbmQgb3V0IHdoaWNoIG1hbnVmYWN0dXJlciBoYXMgdGhlIGhpZ2hlc3QgZGVsYXlzCgpmaXJzdCB3ZSBuZWVkIHRvIGpvaW4gZmxpZ2h0cyB3aXRoIHBsYW5lcwoKYGBge3J9CmZsaWdodF9wbGFuZXMgPC0gbGVmdF9qb2luKGZsaWdodHMsIHBsYW5lcywgYnkgPSAidGFpbG51bSIpCgpmbGlnaHRfcGxhbmVzICU+JSBncm91cF9ieShtYW51ZmFjdHVyZXIpICU+JSBzdW1tYXJpc2UoZGVsYXlfcGVyX2ZsaWdodCA9IHN1bShhcnJfZGVsYXksIG5hLnJtID0gVFJVRSkvIG4oKSxudW1iZXJfb2ZfZmxpZ2h0cyA9IG4oKSkgJT4lIGFycmFuZ2UoZGVzYyhkZWxheV9wZXJfZmxpZ2h0KSkKCmBgYAoKIyMgbGV0cyBmaW5kIG91dCB3aGljaCBhaXJsaW5lIGhhcyB0aGUgaGlnaGVzdCBkZWxheXMKZmlyc3Qgd2UgbmVlZCB0byBqb2luIGZsaWdodHMgd2l0aCBwbGFuZXMKCmBgYHtyfQpmbGlnaHRfYWlybGluZXMgPC0gbGVmdF9qb2luKGZsaWdodHMsIGFpcmxpbmVzKQoKZmxpZ2h0X2FpcmxpbmVzICU+JSBncm91cF9ieShuYW1lKSAlPiUgc3VtbWFyaXNlKGRlbGF5X3Blcl9mbGlnaHQgPSBzdW0oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpLyBuKCksbnVtYmVyX29mX2ZsaWdodHMgPSBuKCkpICU+JSBhcnJhbmdlKGRlc2MoZGVsYXlfcGVyX2ZsaWdodCkpCgpgYGAKCgoKIyMgbG9uZyBhbmQgd2lkZSBkYXRhLmZyYW1lcwoKZm9yIHNvbWUgb3BlcmF0aW9ucyB0aGUgdGlkeSB3aWRlIGZvcm1hdCBpcyBub3Qgc3VpdGFibGUgYXMgaW5wdXQgdG8gYW4gb3BlcmF0aW9uLCB0aGVuIGEgImxvbmciIHZlcnNpb24gb2YgdGhlIGRhdGEuZnJhbWUgY2FuIGJlIGdlbmVyYXRlZCB1c2luZyB0aGUgIm1lbHQiIGNvbW1hbmQuCgoKQSBmdXJ0aGVyIGV4YW1wbGUgd2lsbCBiZSBzaG93biBpbiAqKkV1cm9wZUxlYWd1ZVRyYW5zZmVycy5SbWQqKiBhbmQgZnVydGhlciBpbmZvcm1hdGlvbiBvbiB0aGUgdG9waWMgY2FuIGJlIGZvdW5kIGF0IGh0dHA6Ly9zZWFuYW5kZXJzb24uY2EvMjAxMy8xMC8xOS9yZXNoYXBlLmh0bWwgCgpgYGB7cn0KbGlicmFyeShyZXNoYXBlMikKbmFtZXMoYWlycXVhbGl0eSkgPC0gdG9sb3dlcihuYW1lcyhhaXJxdWFsaXR5KSkKYXFtIDwtIG1lbHQoYWlycXVhbGl0eSwgaWQ9YygibW9udGgiLCAiZGF5IiksCiAgdmFyaWFibGUubmFtZSA9ICJjbGltYXRlX3ZhcmlhYmxlIiwgCiAgdmFsdWUubmFtZSA9ICJjbGltYXRlX3ZhbHVlIikKaGVhZChhaXJxdWFsaXR5KQpoZWFkKGFxbSkKdGFpbChhcW0pCgpgYGAKCkNhc3QgZnVuY3Rpb25zIGNhc3QgKGRldXRzY2g6IGdpZcOfZW4pIGEgbW9sdGVuIGRhdGEgZnJhbWUgaW50byBhbiBhcnJheSBvciBkYXRhIGZyYW1lLgppcyB0aGUgcmV2ZXJzZSBmdW5jdGlvbiBvZiBtZWx0IGFuZCB3aWxsIGJlIHVzZWQgaW4gKipFdXJvcGVMZWFndWVUcmFuc2ZlcnMuUm1kKioKCmBgYHtyfQphcXcgPC0gZGNhc3QoYXFtLCBtb250aCArIGRheSB+IGNsaW1hdGVfdmFyaWFibGUpCmhlYWQoYXF3KSAKYGBgCgoKIyMgbGFzdCB0aGluZyB3ZSBuZWVkIGZvciBFdXJvcGVMZWFndWVUcmFuc2ZlcnMuUm1kCgoqKmdyZXBsKiogcmV0dXJucyBhIGxvZ2ljIHZlY3RvciBnaXZlbiBhbiBleHByZXNzaW9uCgpgYGB7cn0KbGV0dGVycwpncmVwKCJbYS1jXSIsIGxldHRlcnMpCmdyZXAoIlthLXpdIiwgbGV0dGVycykKZ3JlcGwoIlthLWNdIiwgbGV0dGVycykKZ3JlcGwoIlthLXpdIiwgbGV0dGVycykKCmBgYAoKCiMgTGV0cyBkaXZlIGludG8gc29tZSBjb2RlCgoqKkV1cm9wZUxlYWd1ZVRyYW5zZmVycy5SbWQqKgo=